/* 
* Bue 的指令 模板 的解析器
*/

// 自定义错误
class bError extends Error {
  constructor(meg) {
    super(meg)
    this.name = "BueError"
  }
}
// 订阅者 Watcher 给指令模板 绑定的属性 添加 watcher
class Watcher {
  constructor (vm, exp, cb) {
    this.vm = vm // 指向Bue的作用域(Bue实例)
    this.exp = exp // 绑定属性的key
    this.cb = cb // 闭包
    this.value = this.get() // 实例Watcher 就会触发get() ，该this.value用于存放 更新前（初始化）的数据
  }
  get () {
    Dep.target = this // 缓存当前 Watcher实例
   /* 
   *  强制执行监听器（propsHijack函数里 属性的 Object.defineProperty 中的）的get函数 
   *  就是让 该watcher 获取 被监视的属性的值  
   */
  /* 注意： 这一步，中的 this.vm.data[this.exp] 就会立马触发 该属性的 [[get]]， 会把当前 Watcher实例 插入Dep 的 Watcher队列中*/
    const value = this.vm.data[this.exp]
    Dep.target = null // value 拿到值后，就释放自己。方便下一个 watcher实例缓存
    return value
  }
  run() {
    const value = this.vm.data[this.exp]
    const oldVal = this.value
    if (value !== oldVal) {
      this.value = value
      this.cb.call(this.vm,value,oldVal)
    }
  }
  update() {
    this.run()
  }
}
class Compile{
  constructor(el, vm) {
    this.vm = vm,
    this.el = document.querySelector(el)
    this.fragment = null
    this.init()
  }
  init() {
    if(this.el) {
      this.fragment = this.nodeToFragment(this.el)
      this.compileElement(this.fragment)
      /* 
      * 注意： 
      * 这里之所以 要使用appendChild 往挂载的节点上 添加 解析后的节点
      * 是因为 原生的节点都被 appendChild 到 fragment上面去了
      * appendChild 方法是移动节点 到调用节点对象的 子节点上，并不是复制
      *  所有 在下面的循环中 el.firstChild 每个循环都有新的值。因为el的节点被 移动到 fragment上了
      */
      this.el.appendChild(this.fragment)
    } else {
      throw new bError('当前挂载元素不存在！')
    }
  }
  // 把真实的el节点下所有子节点 存到 fragment 中 方便 compile解析 可以理解为 虚拟dom
  nodeToFragment(el) {
    // 创建个虚拟节点先。
    let fragment = document.createDocumentFragment()
    let child = el.firstChild
    while (child) {
      // 将原生Dom元素 移入 fragment 中 构造 dom树
      fragment.appendChild(child)
      child = el.firstChild
    }
    return fragment
  }
  // 给各模板 和指令 匹配解析器
  compileElement(el) {
    var childNodes = el.childNodes;
    Array.from(childNodes).forEach( (node) => {
      // 匹配 => lText {{template}} rText
      // 下标 lText => 1; template => 2; rText => 3
      var reg = /(.*)\{\{(.*)\}\}(.*)/
      var text = node.textContent;
      if (this.isElementNode(node)) {  
          this.compileDirective(node);
      } else if (this.isTextNode(node) && reg.test(text)) {
          this.compileText(node, reg.exec(text));
      }

      if (node.childNodes && node.childNodes.length) {
          this.compileElement(node);
      }
    });
  }
  // 解析模板的解析器, 只解析 {{}} 中的，保留普通的text
  compileText(node, exp) {
    const templateText = this.vm[exp[2]]
    const leftText = exp[1]
    const rightText = exp[3]
    // 格式化 node的 text, 避免显示 undefined
    this.updateText(node, templateText, leftText, rightText)
    new Watcher(this.vm, exp[2], value => {
      this.updateText(node, value, leftText, rightText)
    })
  }
  // 解析指令的解析器
  compileDirective(node) {
    let nodeAttrs = node.attributes
    Array.from(nodeAttrs).forEach(attr => {
      let attrName = attr.name
      if (this.isDirective(attrName)){
        // 指令的 值
        let exp = attr.value
        // ‘b-’ 后面的指令 可能是 on 或 model
        let dir = attrName.substring(2)
        if (this.isEventDirective(dir)) { // 事件指令
          this.compileEvent(node, this.vm, exp, dir)
        }
        if (this.isModelDirective(dir)){  // 双向绑定指令
          this.compileModel(node, exp)
        }
        // 解析完后 在节点上 删除这个指令，它只是为了给解析器指路而已，现在他的工作完成了。
        node.removeAttribute(attrName)
      } 
    })
  }
  // 事件指令解析
  compileEvent(node,vm,exp,dir) {
    // 获取事件类型
    let eventType = dir.split(':')[1]
    let cb = vm.methods && vm.methods[exp]
    if (eventType && cb) {
      node.addEventListener(eventType, cb.bind(vm), false)
    }
  }
  // 双向绑定 model指令解析 
  compileModel(node, exp){
    let val = this.vm[exp]
    // 格式化 node的 value, 避免显示 undefined
    this.modelUpdater(node,val)
    new Watcher(this.vm,exp, value => {
      this.modelUpdater(node,value)
    })
    // input 框的双向绑定
    node.addEventListener('input', (e) => {
      let newValue = e.target.value
      if (val === newValue) return
      this.vm[exp] = newValue
      val = newValue
    })
  }
  /* 一些工具方法 */
  isElementNode (node) {
    return node.nodeType === 1
  }
  isTextNode (node) {
    return node.nodeType === 3
  }
  isDirective (attr) {
    return attr.indexOf('b-') === 0
  }
  isEventDirective (dir) {
    return  dir.indexOf('on:') === 0
  }
  isModelDirective (dir) {
    return dir.indexOf('model') === 0
  }
  // 格式化 templateText, 避免显示 undefined, 并拼接上左右普通的 text
  updateText (node, value, leftText, rightText) { 
    let templateText = typeof value === undefined ? '' : value
    // node.textContent =  leftText.concat(templateText, rightText)
    node.textContent =  leftText + templateText + rightText
  }
  modelUpdater (node, value) { // 格式化 value, 避免显示 undefined
    node.value = typeof value === undefined ? '' : value
  }
}



